 //===============================
//========    Я Д Р О ВЕДРО   ========
//===============================

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;

namespace SNKernel
{
    /// <summary>
    /// Ядро, выполняющее работу над внутренней структурой сем. сети и несущее ответственность за её целостность
    /// </summary>
    [Serializable]
    public class CNet
    {
        #region Переменные

        public static string ErrMsg = "What a fuck?";

        private IList<CNode> _nodes = new List<CNode>();
        private IList<CArc> _arcs = new List<CArc>();
        public static string Unknown = "#Неизвестно";

        #endregion

        #region Списки служебных дуг и рёбер

        /// <summary>
        /// Получает отсортированный список всех доступных на данный момент именованных дуг.
        /// Как минимум, получает отсортированный список всех системных дуг.
        /// </summary>
        public IEnumerable<string> AllArcNames
        {
            get
            {
                var tmp = ReservedArcs;
                var answer = new List<string>();

                foreach (var arc in _arcs)
                    if (answer.FindIndex(str => arc.Name().Trim().ToUpper() == str.Trim().ToUpper()) < 0 && !IsUnnamed(arc.Name()))
                        answer.Add(arc.Name().Trim());
                foreach (var arc in tmp)
                    if (answer.FindIndex(str => arc.Trim().ToUpper() == str.Trim().ToUpper()) < 0 && !IsUnnamed(arc))
                        answer.Add(arc);

                answer.Sort();
                return answer;
            }
        }

        public static IList<string> ReservedArcs
        {
            get
            {
                return new List<string>
                                 {
                                     "#is_a",
                                     "#relations",
                                     "#main_relation",
                                     "#types",
                                     "#objects",
                                     "#name",
                                     "#from",
                                     "#to",
                                     "#has_part",
                                     "#goal"
                                 };
            }
        }


        /// <summary>
        /// Получает список системных вершин
        /// </summary>
        public static IList<string> ReservedNodes
        {
            get
            {
                return new List<string> { "#SYSTEM" };
            }
        }

        #endregion

        #region AutoInc's

        /// <summary>
        /// Базовый автоинкремент.
        /// Возвращает имя в виде "#..." для объекта INamedClass.
        /// </summary>
        /// <param name="names">Список INamedClass</param>
        /// <returns></returns>
        private static string AutoInc(IEnumerable<INamedClass> names)
        {
            int tmp;
            var strs = names.Where(name => !string.IsNullOrEmpty(name.Name()) && name.Name()[0] == '#' && int.TryParse(name.Name().Substring(1), out tmp));
            if (strs.Count() == 0)
                return "#0";
            tmp = 0;
            var aa = strs.Where(str => int.TryParse(str.Name().Substring(1), out tmp)).ToList();
            foreach (var str in aa.Where(str => int.Parse(str.Name().Substring(1)) > tmp))
                tmp = int.Parse(str.Name().Substring(1));
            return "#" + (tmp + 1);
        }

        /// <summary>
        /// Автоинкремент для дуг.
        /// Возвращает имя дуги в виде "#...".
        /// </summary>
        /// <param name="node">Имя вершины, из которой выходит дуга</param>
        /// <returns></returns>
        private string AutoIncArc(string node)
        {
            var strs = _arcs.Where(arc => arc.From.Trim().ToUpper() == node.Trim().ToUpper()).ToList();
            return AutoInc(strs);
        }

        /// <summary>
        /// Получает новое имя для вершины в виде "#..."
        /// </summary>
        private string AutoIncNode
        {
            get { return AutoInc(_nodes); }
        }

        #endregion

        #region Добавление

        /// <summary>
        /// Добавляет новую вершину. 
        /// Возвращает имя вершины в виде "#...".
        /// </summary>
        /// <returns></returns>
        public string NewNode()
        {
            var tmpName = AutoIncNode;
            Atom(tmpName);
            return tmpName;
        }

        /// <summary>
        /// Добавляет дугу ARC, ведущую из вершины FROM в вершину TO
        /// </summary>
        /// <param name="from">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="arc">Имя дуги</param>
        /// <param name="to">Вершина, В которую входит дуга</param>
        private void SetAttr(string from, string arc, string to)
        {
            if (ArcExists(from, arc))
                return;
            var tmp = new CArc { From = from, To = to };
            tmp.ReName(arc);
            _arcs.Add(tmp);
        }

        /// <summary>
        /// Добавляет дугу, ведущую из вершины FROM в вершину TO.
        /// Возвращает имя дуги виде "#...".
        /// </summary>
        /// <param name="from">Вершина ИЗ которой выходит дуга</param>
        /// <param name="to">Вершина, В которую входит дуга</param>
        /// <returns></returns>
        public string SetAttr(string from, string to)
        {
            var name = AutoIncArc(from);
            SetAttr(from, name, to);
            return name;
        }

        /// <summary>
        /// Проверяет, есть ли вершина в списке, и добавляет, если она не существует
        /// </summary>
        /// <param name="name">Имя вершины</param>
        public void Atom(string name)
        {
            if (!_nodes.Any(node => node.Name().ToUpper().Trim() == name.Trim().ToUpper()))
            {
                var tmp = new CNode();
                tmp.ReName(name.Trim());
                _nodes.Add(tmp);
            }
        }

        #endregion

        #region Изменение

        /// <summary>
        /// Переименовывает дугу OLDNAME, ведущую из вершины FROM, в дугу с именем NEWNAME.
        /// Возвращает true, если всё ОК.
        /// Возвращает false, если дуга с таким именем уже существует.
        /// </summary>
        /// <param name="from">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="oldName">Старое имя дуги</param>
        /// <param name="newName">Новое имя дуги</param>
        /// <returns></returns>
        public bool RenameArc(string from, string oldName, string newName)
        {
            if (!ArcExists(from, oldName))
                throw new Exception(ErrMsg);
            if (ArcExists(from, newName) && oldName.Trim().ToUpper() != newName.Trim().ToUpper())
                return false;
            _arcs.First(
                arc =>
                arc.Name().Trim().ToUpper() == oldName.Trim().ToUpper() &&
                from.Trim().ToUpper() == arc.From.Trim().ToUpper()).ReName(newName.Trim());
            return true;
        }

        /// <summary>
        /// Меняет ИСХОДЯЩЕЕ направление дуги.
        /// В параметре NEWARCNAME возвращается имя дуги (возможно изменённое).
        /// Возвращает true, если всё ОК.
        /// Возвращает false, если дуга с таким именем уже существует.
        /// </summary>
        /// <param name="from">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="arcName">Имя дуги</param>
        /// <param name="newFrom">Новая вершина, ИЗ которой будет выходить дуга</param>
        /// <param name="newArcName">Новое имя дуги (ПАРАМЕТР ПО ССЫЛКЕ)</param>
        /// <returns></returns>
        public bool ChangeArcDirectionFrom(string from, string arcName, string newFrom, out string newArcName)
        {
            if (!ArcExists(from, arcName))
                throw new Exception(ErrMsg);
            if (ArcExists(newFrom, arcName) && IsUnnamed(arcName))
                newArcName = AutoIncArc(newFrom);
            else
                newArcName = arcName;
            if (ArcExists(newFrom, newArcName))
                return false;
            var tmp = _arcs.First(
                arc =>
                arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper() &&
                from.Trim().ToUpper() == arc.From.Trim().ToUpper());
            tmp.From = newFrom.Trim();
            tmp.ReName(newArcName);
            return true;
        }

        /// <summary>
        /// Меняет ВХОДЯЩЕЕ направление дуги.
        /// Возвращает true, если всё ОК.
        /// </summary>
        /// <param name="from">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="arcName">Имя дуги</param>
        /// <param name="newTo">Новая вершина, В которую будет входить дуга</param>
        /// <returns></returns>
        public bool ChangeArcDirectionTo(string from, string arcName, string newTo)
        {
            if (!ArcExists(from, arcName))
                throw new Exception(ErrMsg);
            _arcs.First(
                arc =>
                arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper() &&
                from.Trim().ToUpper() == arc.From.Trim().ToUpper()).To = newTo.Trim();
            return true;
        }

        /// <summary>
        /// Переименовывает вершину OLDNAME именем NEWNAME.
        /// Возвращает true, если всё ОК.
        /// Возвращает false, если имя занято.
        /// </summary>
        /// <param name="oldName">Старое имя вершины</param>
        /// <param name="newName">Новое имя вершины</param>
        /// <returns></returns>
        public bool RenameNode(string oldName, string newName)
        {
            if (!_nodes.Any(node => node.Name().Trim().ToUpper() == oldName.Trim().ToUpper()))
                throw new Exception(ErrMsg);
            if (oldName.ToUpper().Trim() == newName.ToUpper().Trim())
            {
                _nodes.First(node => node.Name().Trim().ToUpper() == oldName.Trim().ToUpper()).ReName(newName.Trim());
                foreach (var arc in _arcs)
                {
                    if (arc.From.Trim().ToUpper() == oldName.Trim().ToUpper())
                        arc.From = newName.Trim();
                    if (arc.To.Trim().ToUpper() == oldName.Trim().ToUpper())
                        arc.To = newName.Trim();
                }
                return true;
            }
            if (_nodes.Any(node => node.Name().Trim().ToUpper() == newName.Trim().ToUpper()))
                return false;
            _nodes.First(node => node.Name().Trim().ToUpper() == oldName.Trim().ToUpper()).ReName(newName.Trim());
            foreach (var arc in _arcs)
            {
                if (arc.From.Trim().ToUpper() == oldName.Trim().ToUpper())
                    arc.From = newName.Trim();
                if (arc.To.Trim().ToUpper() == oldName.Trim().ToUpper())
                    arc.To = newName.Trim();
            }
            return true;
        }

        #endregion

        #region Удаление

        /// <summary>
        /// Удаляет вершину
        /// </summary>
        /// <param name="name">Имя вершины</param>
        public void RemoveNode(string name)
        {
            if (!_nodes.Any(nm => nm.Name().ToUpper().Trim() == name.Trim().ToUpper()))
                throw new Exception(ErrMsg);
            _nodes = _nodes.Where(node => node.Name().Trim().ToUpper() != name.Trim().ToUpper()).ToList();
            _arcs =
                _arcs.Where(
                    arc =>
                    arc.From.Trim().ToUpper() != name.Trim().ToUpper() &&
                    arc.To.Trim().ToUpper() != name.Trim().ToUpper()).ToList();
        }

        /// <summary>
        /// Удаляет дугу ARCNAME, выходящую из вершины NAMENODE
        /// </summary>
        /// <param name="nameNode">Вершина, ИЗ которой выходит удаляемая дуга</param>
        /// <param name="arcName">Имя удаляемой дуги</param>
        public void ResetAttr(string nameNode, string arcName)
        {
            if (!ArcExists(nameNode, arcName))
                throw new Exception(ErrMsg);
            _arcs =
                _arcs.Where(
                    arc =>
                    !(arc.From.Trim().ToUpper() == nameNode.Trim().ToUpper() &&
                    arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper())).ToList();
        }

        public void RemoveNodesByPredicate(Func<string, bool> predicate)
        {
            _nodes.Where(node=>predicate(node.Name())).ToList().ForEach(node=>RemoveNode(node.Name()));
        }

        #endregion

        #region Проверки (существования, именованости, ...)

        /// <summary>
        /// Возвращает, если существует дуга с именем ARC, выходящая из вершины FROM
        /// </summary>
        /// <param name="from">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="arc">Имя дуги</param>
        /// <returns></returns>
        private bool ArcExists(string from, string arc)
        {
            return
                _arcs.Any(
                    lstArc =>
                    lstArc.Name().Trim().ToUpper() == arc.Trim().ToUpper() &&
                    lstArc.From.Trim().ToUpper() == from.Trim().ToUpper());
        }

        /// <summary>
        /// Возвращает, если вершина существует 
        /// </summary>
        /// <param name="nodeName">Имя вершины</param>
        /// <returns></returns>
        public bool NodeExists(string nodeName)
        {
            return _nodes.Any(node => node.Name().Trim().ToUpper() == nodeName.Trim().ToUpper());
        }

        /// <summary>
        /// Возвращает, если вершина/дуга является неименованной
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool IsUnnamed(string name)
        {
            int d;
            return name.Trim()[0] == '#' && int.TryParse(name.Trim().Substring(1), out d);
        }

        #endregion

        #region Разыменование

        /// <summary>
        /// Выполняет разыменование вершины.
        /// Возвращает новое имя (либо старое имя, если вершина неименованная).
        /// </summary>
        /// <param name="name">Имя вершины</param>
        /// <returns></returns>
        public string UnnameNode(string name)
        {
            if (IsUnnamed(name))
                return name;
            var ans = AutoIncNode;
            _nodes.First(node => node.Name().Trim().ToUpper() == name.Trim().ToUpper()).ReName(ans);
            return ans;
        }

        /// <summary>
        /// Выполняет разыменование дуги ARCNAME, исходящей из вершины NODENAME.
        /// Возвращает новое имя (либо старое имя, если дуга неименованная).
        /// </summary>
        /// <param name="nodeName">Вершина, ИЗ которой выходит дуга</param>
        /// <param name="arcName">Имя дуги</param>
        /// <returns></returns>
        public string UnnameArc(string nodeName, string arcName)
        {
            if (IsUnnamed(arcName))
                return arcName;
            var ans = AutoIncArc(nodeName);
            _arcs.First(
                arc =>
                arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper() &&
                arc.From.Trim().ToUpper() == nodeName.Trim().ToUpper()).ReName(ans);
            return ans;
        }

        #endregion

        #region Сохранение/загрузка

        /// <summary>
        /// Сериализует собственный экземпляр в файл
        /// </summary>
        /// <param name="fullFileName">Имя файла</param>
        public void SaveToFile(string fullFileName)
        {
            var bf = new BinaryFormatter();
            var fs = new FileStream(fullFileName, FileMode.Create);
            bf.Serialize(fs, this);
            fs.Close();
        }

        /// <summary>
        /// Десериализует собственный экземпляр из файла.
        /// Возвращает себя любимого.
        /// </summary>
        /// <param name="fullFileName">Имя файла</param>
        /// <returns></returns>
        public static CNet LoadFromFile(string fullFileName)
        {
            var bf = new BinaryFormatter();
            var fs = new FileStream(fullFileName, FileMode.Open);
            var ans = (CNet)bf.Deserialize(fs);
            fs.Close();
            return ans;
        }

        /// <summary>
        /// Получает сериализованный в памяти собственный экземпляр (в виде строки)
        /// </summary>
        public string AsXml
        {
            get
            {
                var bf = new BinaryFormatter();
                var ms = new MemoryStream();
                bf.Serialize(ms, this);

                var ans = System.Text.Encoding.Default.GetString(ms.GetBuffer());
                ms.Close();
                return ans;

                //var trixml = new XmlSerializer(typeof(CNet), new[] { typeof(List<CArc>), typeof(List<CNode>), typeof(string) });
                //var ms = new MemoryStream();
                //trixml.Serialize(ms, this);
                //var ans = System.Text.Encoding.Default.GetString(ms.GetBuffer());
                //ms.Close();
                //return ans;
            }
        }

        /// <summary>
        /// Десериализует собственный экземпляр из памяти (из строки).
        /// Возвращает себя любимого.
        /// </summary>
        /// <param name="net">Строка, содержащая экземпляр класса CNet</param>
        /// <returns></returns>
        public static CNet LoadFromXml(string net)
        {
            var bf = new BinaryFormatter();
            var ms = new MemoryStream(System.Text.Encoding.Default.GetBytes(net));
            var ans = (CNet)bf.Deserialize(ms);
            ms.Close();
            return ans;

            //var trixml = new XmlSerializer(typeof(CNet), new[] { typeof(List<CArc>), typeof(List<CNode>), typeof(string) });
            //var ms = new MemoryStream(System.Text.Encoding.Default.GetBytes(net));
            //var ans = (CNet)trixml.Deserialize(ms);
            //ms.Close();
            //return ans;
        }

        #endregion

        #region Дебаг

        public void PrintNodes(string fullFileName)
        {
            var fs = new FileStream(fullFileName, FileMode.Create);
            fs.Close();

            File.WriteAllLines(fullFileName, _nodes.Select(node => node.Name()).ToArray());
        }
        public void PrintArcs(string fullFileName)
        {
            var fs = new FileStream(fullFileName, FileMode.Create);
            fs.Close();
            var lst = new List<string>();
            foreach (var arc in _arcs)
            {
                lst.Add(new string(' ', 50));
                lst[lst.Count - 1] = lst[lst.Count - 1].Remove(0, arc.From.Trim().Length);
                lst[lst.Count - 1] = lst[lst.Count - 1].Insert(0, arc.From.Trim());

                lst[lst.Count - 1] = lst[lst.Count - 1].Remove(25, arc.Name().Trim().Length);
                lst[lst.Count - 1] = lst[lst.Count - 1].Insert(25, arc.Name().Trim());

                lst[lst.Count - 1] = lst[lst.Count - 1].Insert(50, arc.To.Trim());
            }
            File.WriteAllLines(fullFileName, lst.ToArray());
        }
        public void ScanNodes(string fullFileName)
        {
            var lst = File.ReadAllLines(fullFileName);
            _nodes.Clear();
            foreach (var s in lst)
            {
                var node = new CNode();
                node.ReName(s);
                _nodes.Add(node);
            }
        }
        public void ScanArcs(string fullFileName)
        {
            var lst = File.ReadAllLines(fullFileName);
            _arcs.Clear();
            foreach (var s in lst)
            {
                var tmp = s;
                var index = 0;
                while (!(tmp[index] == ' ' && tmp[index + 1] == ' '))
                    index++;
                var from = tmp.Substring(0, index).Trim();
                tmp = tmp.Substring(index + 1).Trim();

                index = 0;
                while (!(tmp[index] == ' ' && tmp[index + 1] == ' '))
                    index++;
                var name = tmp.Substring(0, index).Trim();
                tmp = tmp.Substring(index + 1).Trim();
                var to = tmp;//.Substring(0, tmp.IndexOf(' '));


                var arc = new CArc { From = from, To = to };
                arc.ReName(name);
                _arcs.Add(arc);
            }
        }

        #endregion

        #region Вывод

        /// <summary>
        /// Возвращает список неименованных вершин, НЕПОСРЕДСТВЕННО достижимых из данной
        /// </summary>
        /// <param name="node">Имя вершины</param>
        /// <returns></returns>
        public IEnumerable<string> GetNodesByUnnamedArcs(string node)
        {
            var lst= (from CArc arc in _arcs where arc.From.Trim().ToUpper() == node.Trim().ToUpper() && IsUnnamed(arc.Name()) select arc.To.Trim()).ToList();
            //lst.Sort();
            return lst;
        }

        /// <summary>
        /// Возвращает имя вершины, непосредственно достижимой из вершины NODENAME по дуге ARCNAME
        /// </summary>
        /// <param name="nodeName">Имя вершины</param>
        /// <param name="arcName">Имя дуги</param>
        /// <returns></returns>
        public string GetNextNode(string nodeName, string arcName)
        {
            try
            {
                return
                    _arcs.First(
                        arc =>
                        arc.From.Trim().ToUpper() == nodeName.Trim().ToUpper() &&
                        arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper()).To.Trim();
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Возвращает список вершин, у которых дуга ARCNAME ведёт в вершину ATTRIBUTEVALUE
        /// </summary>
        /// <param name="arcName">Имя дуги</param>
        /// <param name="attributeValue">Имя вершины</param>
        /// <returns></returns>
        public IList<string> GetNodesByAttribute(string arcName, string attributeValue)
        {
            return (from CArc arc in _arcs
                    where
                        arc.Name().Trim().ToUpper() == arcName.Trim().ToUpper() &&
                        arc.To.Trim().ToUpper() == attributeValue.Trim().ToUpper()
                    select arc.From.Trim()).ToList();
        }

        /// <summary>
        /// Возвращает список исходящих из вершины дуг
        /// </summary>
        /// <param name="node">Имя вершины</param>
        /// <returns></returns>
        public IList<string> GetOutputArcsByNode(string node)
        {
            return (from arc in _arcs where arc.From.Trim().ToUpper() == node.Trim().ToUpper() select arc.Name().Trim()).ToList();
        }

        /// <summary>
        /// Возвращает список входящих в вершину дуг
        /// </summary>
        /// <param name="node">Имя вершины</param>
        /// <returns></returns>
        public IList<string> GetInputArcsByNode(string node)
        {
            return (from arc in _arcs where arc.To.Trim().ToUpper() == node.Trim().ToUpper() select arc.Name().Trim()).ToList();
        }

        /// <summary>
        /// Получает системную вершину (либо null в случае ошибки)
        /// </summary>
        public string SystemNode
        {
            get
            {
                var ans = GetNodesByAttribute("#Name", "#System");
                return ans.Count != 1 ? null : ans[0];
            }
        }

        /// <summary>
        /// Проверяет, чтобы все вершины по дуге #Name ссылались на именованные узлы.
        /// Возвращает NULL, если всё ОК (иначе текст ошибки)
        /// </summary>
        /// <returns></returns>
        public string ValidateNames()
        {
            var arcs = _arcs.Where(arc => arc.Name().Trim().ToUpper() == "#NAME").ToList();
            return (from arc in arcs
                    where IsUnnamed(arc.To)
                    select "Узел " + arc.From + " по дуге #Name должен ссылатся на именовонный узел").FirstOrDefault();
        }

        /// <summary>
        /// Возвращает, если ВСЕ вершины удовлетворяют условию данного предиката
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public bool IsTrueForAllNodes(Func<string, bool> predicate)
        {
            return (from node in _nodes select node.Name()).All(predicate);
        }

        public IEnumerable<string> GetAllUserArcsByNode(string node)
        {
            return GetOutputArcsByNode(node).Where(
                str =>
                !IsUnnamed(str) &&
                !ReservedArcs.ToList().Exists(arc => arc.Trim().ToUpper() == str.Trim().ToUpper()));
        }

        #endregion

        #region Для красоты

        private static int MaxLength(IEnumerable<INamedClass> namedClassList)
        {
            return (from namedClassInstance in namedClassList select namedClassInstance.Name().Trim().Length).Max();
        }

        public int MaxLengthNodes
        {
            get
            {
                return MaxLength(_nodes);
            }
        }

        public int MaxLengthArcs
        {
            get
            {
                return MaxLength(_arcs);
            }
        }

        #endregion
    }
}